#pragma once

#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <strings.h>

#include "log.hpp"
#include "Inet_Addr.hpp"

namespace mySocket
{
    const static int g_backlog = 16;
    class Socket;
    typedef Socket* socket_ptr;
    enum
    {
        SOCKET_ERROR = 1,
        BIND_ERROR,
        LISTEN_ERROR,
        CONNECT_ERROR,
        RECV_ERROR,
        SEND_ERROR
    };

    class Socket
    {
    protected:
        virtual void socketCreate() = 0;
        virtual void socketBind(InetAddr serverAddr) = 0;
        virtual void socketListen() = 0;

    public:
        
        void tcpServerInit(InetAddr serverAddr)
        {
            socketCreate();
            socketBind(serverAddr);
            socketListen();
        }

        void tcpClientInit(InetAddr serverAddr)
        {
            socketCreate();
            socketConnect(serverAddr);
        }

        virtual socket_ptr socketAccept(InetAddr *clientAddr) = 0;
        virtual void socketConnect(InetAddr serverAddr) = 0;

        virtual int getSockd() = 0;
        virtual int socketRecv(std::string *massage) = 0;
        virtual int socketSend(const std::string& massage) = 0;
        virtual void socketClose() = 0;
    };

    class tcpSocket : public Socket
    {
    public:
        tcpSocket(int sockfd = -1)
            : _sockfd(sockfd)
        {
        }

        void socketCreate() override
        {
            _sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (_sockfd < 0)
            {
                LOG(FATA, "socket() error");
                exit(SOCKET_ERROR);
            }
        }

        void socketBind(InetAddr serverAddr) override
        {
            struct sockaddr_in localAddr;
            bzero(&localAddr, sizeof(localAddr));
            localAddr.sin_family = AF_INET;
            localAddr.sin_port = serverAddr.Port();
            localAddr.sin_addr.s_addr = inet_addr(serverAddr.IP().c_str());

            int ret = bind(_sockfd, (struct sockaddr *)&localAddr, sizeof(localAddr));
            if (ret < 0)
            {
                LOG(FATA, "bind() error");
                exit(BIND_ERROR);
            }
        }

        void socketListen() override
        {
            int ret = listen(_sockfd, g_backlog);
            if (ret < 0)
            {
                LOG(FATA, "listen() error");
                exit(LISTEN_ERROR);
            }

            LOG(INFO, "serverInit success!!!");
        }

        socket_ptr socketAccept(InetAddr *clientAddr) override
        {
            struct sockaddr_in cAddr;
            bzero(&cAddr, sizeof(cAddr));
            socklen_t cAddrLen = sizeof(cAddr);

            // 接收连接
            int sockfd = accept(_sockfd, (struct sockaddr *)&cAddr, &cAddrLen);
            if (sockfd < 0)
            {
                LOG(ERROR, "accept failed");
                return nullptr;
            }

            *clientAddr = cAddr;
            return new tcpSocket(sockfd);
        }

        void socketConnect(InetAddr serverAddr) override
        {
            struct sockaddr_in server;
            bzero(&server, sizeof(server));
            server.sin_family = AF_INET;
            server.sin_port = serverAddr.Port();
            inet_pton(AF_INET, serverAddr.IP().c_str(), &server.sin_addr);

            int n = connect(_sockfd, (struct sockaddr*)&server, sizeof(server));
            if (n < 0)
                exit(CONNECT_ERROR);
        }

        int getSockd() override
        {
            return _sockfd;
        }

        int socketRecv(std::string *massage) override
        {
            char buffer[1024]{0};
            int n = recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
            if (n > 0)
                *massage += buffer;

            return n;
        }

        int socketSend(const std::string& massage) override
        {
            int n = send(_sockfd, massage.c_str(), massage.size(), 0);
            return n;
        }

        void socketClose() override
        {
            if (_sockfd)
                ::close(_sockfd);
        }

    private:
        int _sockfd;
    };
}